package nachos.threads;

import nachos.machine.*;

/**
  A Semaphore is a synchronization primitive with an unsigned value.
  A semaphore has only two operations:
 
  P(): waits until the semaphore's value is greater than zero,
  then decrements it.
  V(): increments the semaphore's value, and wakes up one thread
  waiting in P() if possible.
  
  Note that this API does not allow a thread to read the value of the
  semaphore directly. Even if you did read the value, the only thing you would
  know is what the value used to be. You don't know what the value is now,
  because by the time you get the value, a context switch might have occurred,
  and some other thread might have called P() or V(), so the
  true value might now be different.
 */
public class Semaphore {
    /**
      Allocate a new semaphore.
     
      Parameters:
			initialValue - the initial value of this semaphore.
    */
    public Semaphore(int initialValue) {value = initialValue;}

    /**
      Atomically wait for this semaphore to become non-zero and decrement it.
    */
    public void P() 
	{
		boolean intStatus = Machine.interrupt().disable();

		if (value == 0) 
		{
			waitQueue.waitForAccess(KThread.currentThread());
			KThread.sleep();
		}
		else 
			value--;
	

		Machine.interrupt().restore(intStatus);
    }

    /**
      Atomically increment this semaphore and wake up at most one other thread
      sleeping on this semaphore.
    */
    public void V() 
	{
		boolean intStatus = Machine.interrupt().disable();

		KThread thread = waitQueue.nextThread();
		if (thread != null) 
			thread.ready();
		else 
			value++;
		
		Machine.interrupt().restore(intStatus);
    }

    private static class PingTest implements Runnable 
	{
		PingTest(Semaphore ping, Semaphore pong) 
		{
			this.ping = ping;
			this.pong = pong;
		}
	
		public void run() 
		{
			for (int i=0; i<10; i++) 
			{
				ping.P();
				pong.V();
			}
		}

		private Semaphore ping;
		private Semaphore pong;
    }

    /**
      Test if this module is working.
    */
    public static void selfTest() 
	{
		Semaphore ping = new Semaphore(0);
		Semaphore pong = new Semaphore(0);

		new KThread(new PingTest(ping, pong)).setName("ping").fork();

		for (int i=0; i<10; i++) 
		{
			ping.V();
			pong.P();
		}
    }

    private int value;
    private ThreadQueue waitQueue = ThreadedKernel.scheduler.newThreadQueue(false);
}
